{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([ 0.02760536, -0.02669347,  0.00619955, -0.04277581], dtype=float32)"
      ]
     },
     "execution_count": 36,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import gym\n",
    "\n",
    "\n",
    "#定义环境\n",
    "class MyWrapper(gym.Wrapper):\n",
    "\n",
    "    def __init__(self):\n",
    "        env = gym.make('CartPole-v1', render_mode='rgb_array')\n",
    "        super().__init__(env)\n",
    "        self.env = env\n",
    "        self.step_n = 0\n",
    "\n",
    "    def reset(self):\n",
    "        state, _ = self.env.reset()\n",
    "        self.step_n = 0\n",
    "        return state\n",
    "\n",
    "    def step(self, action):\n",
    "        state, reward, terminated, truncated, info = self.env.step(action)\n",
    "        done = terminated or truncated\n",
    "        self.step_n += 1\n",
    "        if self.step_n >= 200:\n",
    "            done = True\n",
    "        return state, reward, done, info\n",
    "\n",
    "\n",
    "env = MyWrapper()\n",
    "\n",
    "env.reset()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from matplotlib import pyplot as plt\n",
    "\n",
    "%matplotlib inline\n",
    "\n",
    "\n",
    "#打印游戏\n",
    "def show():\n",
    "    plt.imshow(env.render())\n",
    "    plt.show()\n",
    "\n",
    "\n",
    "show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(1, 75.0)"
      ]
     },
     "execution_count": 38,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import torch\n",
    "import random\n",
    "from IPython import display\n",
    "\n",
    "\n",
    "class PPO:\n",
    "    def __init__(self):\n",
    "        #定义模型\n",
    "        self.model = torch.nn.Sequential(\n",
    "            torch.nn.Linear(4, 128),\n",
    "            torch.nn.ReLU(),\n",
    "            torch.nn.Linear(128, 2),\n",
    "            torch.nn.Softmax(dim=1),\n",
    "        )\n",
    "\n",
    "        self.model_td = torch.nn.Sequential(\n",
    "            torch.nn.Linear(4, 128),\n",
    "            torch.nn.ReLU(),\n",
    "            torch.nn.Linear(128, 1),\n",
    "        )\n",
    "\n",
    "        self.optimizer = torch.optim.Adam(self.model.parameters(), lr=1e-3)\n",
    "        self.optimizer_td = torch.optim.Adam(self.model_td.parameters(),\n",
    "                                             lr=1e-2)\n",
    "        self.loss_fn = torch.nn.MSELoss()\n",
    "\n",
    "    #得到一个动作\n",
    "    def get_action(self, state):\n",
    "        state = torch.FloatTensor(state).reshape(1, 4)\n",
    "        #[1, 4] -> [1, 2]\n",
    "        prob = self.model(state)\n",
    "\n",
    "        #根据概率选择一个动作\n",
    "        action = random.choices(range(2), weights=prob[0].tolist(), k=1)[0]\n",
    "\n",
    "        return action\n",
    "\n",
    "    def _get_advantages(self, deltas):\n",
    "        advantages = []\n",
    "\n",
    "        #反向遍历deltas\n",
    "        s = 0.0\n",
    "        for delta in deltas[::-1]:\n",
    "            s = 0.98 * 0.95 * s + delta\n",
    "            advantages.append(s)\n",
    "\n",
    "        #逆序\n",
    "        advantages.reverse()\n",
    "        return advantages\n",
    "\n",
    "    def train(self, states, rewards, actions, next_states, overs):\n",
    "        #states -> [b, 4]\n",
    "        #rewards -> [b, 1]\n",
    "        #actions -> [b, 1]\n",
    "        #next_states -> [b, 4]\n",
    "        #overs -> [b, 1]\n",
    "\n",
    "        #计算values和targets\n",
    "        #[b, 4] -> [b, 1]\n",
    "        values = self.model_td(states)\n",
    "\n",
    "        #[b, 4] -> [b, 1]\n",
    "        targets = self.model_td(next_states) * 0.98\n",
    "        targets *= (1 - overs)\n",
    "        targets += rewards\n",
    "\n",
    "        #[b, 1]\n",
    "        deltas = (targets - values).squeeze(dim=1).tolist()\n",
    "        advantages = self._get_advantages(deltas)\n",
    "        advantages = torch.FloatTensor(advantages).reshape(-1, 1)\n",
    "\n",
    "        #取出每一步的动作概率\n",
    "        #[b, 2] -> [b, 2] -> [b, 1]\n",
    "        old_probs = self.model(states)\n",
    "        old_probs = old_probs.gather(1, actions)\n",
    "        old_probs = old_probs.detach()\n",
    "\n",
    "        #每批数据反复训练10次\n",
    "        for _ in range(10):\n",
    "            #[b, 4] -> [b, 2]\n",
    "            new_probs = self.model(states)\n",
    "\n",
    "            #[b, 2] -> [b, 1]\n",
    "            new_probs = new_probs.gather(1, actions)\n",
    "            new_probs = new_probs\n",
    "\n",
    "            #[b, 1] - [b, 1] -> [b, 1]\n",
    "            ratios = new_probs / old_probs\n",
    "\n",
    "            #[b, 1] * [b, 1] -> [b, 1]\n",
    "            surr1 = ratios * advantages\n",
    "\n",
    "            #[b, 1] * [b, 1] -> [b, 1]\n",
    "            surr2 = torch.clamp(ratios, 0.8, 1.2) * advantages\n",
    "\n",
    "            loss = -torch.min(surr1, surr2)\n",
    "            loss = loss.mean()\n",
    "\n",
    "            values = self.model_td(states)\n",
    "            loss_td = self.loss_fn(values, targets.detach())\n",
    "\n",
    "            #更新参数\n",
    "            self.optimizer.zero_grad()\n",
    "            loss.backward()\n",
    "            self.optimizer.step()\n",
    "\n",
    "            self.optimizer_td.zero_grad()\n",
    "            loss_td.backward()\n",
    "            self.optimizer_td.step()\n",
    "\n",
    "    def get_data(self):\n",
    "        states = []\n",
    "        rewards = []\n",
    "        actions = []\n",
    "        next_states = []\n",
    "        overs = []\n",
    "\n",
    "        #初始化游戏\n",
    "        state = env.reset()\n",
    "\n",
    "        #玩到游戏结束为止\n",
    "        over = False\n",
    "        while not over:\n",
    "            #根据当前状态得到一个动作\n",
    "            action = self.get_action(state)\n",
    "\n",
    "            #执行动作,得到反馈\n",
    "            next_state, reward, over, _ = env.step(action)\n",
    "\n",
    "            #记录数据样本\n",
    "            states.append(state)\n",
    "            rewards.append(reward)\n",
    "            actions.append(action)\n",
    "            next_states.append(next_state)\n",
    "            overs.append(over)\n",
    "\n",
    "            #更新游戏状态,开始下一个动作\n",
    "            state = next_state\n",
    "\n",
    "        #[b, 4]\n",
    "        states = torch.FloatTensor(states).reshape(-1, 4)\n",
    "        #[b, 1]\n",
    "        rewards = torch.FloatTensor(rewards).reshape(-1, 1)\n",
    "        #[b, 1]\n",
    "        actions = torch.LongTensor(actions).reshape(-1, 1)\n",
    "        #[b, 4]\n",
    "        next_states = torch.FloatTensor(next_states).reshape(-1, 4)\n",
    "        #[b, 1]\n",
    "        overs = torch.LongTensor(overs).reshape(-1, 1)\n",
    "\n",
    "        return states, rewards, actions, next_states, overs\n",
    "\n",
    "    def test(self, play):\n",
    "        #初始化游戏\n",
    "        state = env.reset()\n",
    "\n",
    "        #记录反馈值的和,这个值越大越好\n",
    "        reward_sum = 0\n",
    "\n",
    "        #玩到游戏结束为止\n",
    "        over = False\n",
    "        while not over:\n",
    "            #根据当前状态得到一个动作\n",
    "            action = self.get_action(state)\n",
    "\n",
    "            #执行动作,得到反馈\n",
    "            state, reward, over, _ = env.step(action)\n",
    "            reward_sum += reward\n",
    "\n",
    "            #打印动画\n",
    "            if play and random.random() < 0.2:  #跳帧\n",
    "                display.clear_output(wait=True)\n",
    "                show()\n",
    "\n",
    "        return reward_sum\n",
    "\n",
    "\n",
    "teacher = PPO()\n",
    "\n",
    "teacher.train(*teacher.get_data())\n",
    "\n",
    "teacher.get_action([1, 2, 3, 4]), teacher.test(play=False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0 20.8\n",
      "50 169.0\n",
      "100 141.1\n",
      "150 98.4\n",
      "200 200.0\n",
      "250 200.0\n",
      "300 200.0\n",
      "350 200.0\n",
      "400 200.0\n",
      "450 186.4\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "200.0"
      ]
     },
     "execution_count": 39,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 训练一个老师模型\n",
    "for i in range(500):\n",
    "    teacher.train(*teacher.get_data())\n",
    "\n",
    "    if i % 50 == 0:\n",
    "        test_result = sum([teacher.test(play=False) for _ in range(10)]) / 10\n",
    "        print(i, test_result)\n",
    "\n",
    "teacher.test(play=False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(torch.Size([200, 4]), torch.Size([200, 1]))"
      ]
     },
     "execution_count": 40,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "#使用训练好的模型获取一批教师数据\n",
    "teacher_states, _, teacher_actions, _, _ = teacher.get_data()\n",
    "\n",
    "#删除教师,只留下教师的数据就可以了\n",
    "del teacher\n",
    "\n",
    "teacher_states.shape, teacher_actions.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<__main__.PPO at 0x24d9498d3d0>"
      ]
     },
     "execution_count": 41,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "#初始化学生模型\n",
    "student = PPO()\n",
    "\n",
    "student"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([[0.5444],\n",
       "        [0.5491]], grad_fn=<SigmoidBackward0>)"
      ]
     },
     "execution_count": 42,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "#定义鉴别器网络,它的任务是鉴定一批数据是出自teacher还是student\n",
    "class Discriminator(torch.nn.Module):\n",
    "    def __init__(self):\n",
    "        super().__init__()\n",
    "        self.sequential = torch.nn.Sequential(\n",
    "            torch.nn.Linear(6, 128),\n",
    "            torch.nn.ReLU(),\n",
    "            torch.nn.Linear(128, 1),\n",
    "            torch.nn.Sigmoid(),\n",
    "        )\n",
    "\n",
    "    def forward(self, states, actions):\n",
    "        one_hot = torch.nn.functional.one_hot(actions.squeeze(dim=1),\n",
    "                                              num_classes=2)\n",
    "\n",
    "        cat = torch.cat([states, one_hot], dim=1)\n",
    "\n",
    "        return self.sequential(cat)\n",
    "\n",
    "\n",
    "discriminator = Discriminator()\n",
    "\n",
    "discriminator(torch.randn(2, 4), torch.ones(2, 1).long())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "metadata": {},
   "outputs": [],
   "source": [
    "state = env.reset()\n",
    "state = torch.tensor(state)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([1])"
      ]
     },
     "execution_count": 44,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "action=torch.tensor(0).long()\n",
    "action.reshape(1).shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([2])"
      ]
     },
     "execution_count": 45,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "one_hot=torch.nn.functional.one_hot(action.squeeze(),num_classes=2)\n",
    "one_hot.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([-0.0119,  0.0498, -0.0037,  0.0163,  1.0000,  0.0000])"
      ]
     },
     "execution_count": 46,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "cat = torch.cat([state,one_hot])\n",
    "cat"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([200, 4])"
      ]
     },
     "execution_count": 47,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "teacher_states.shape\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 50,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([200, 1])"
      ]
     },
     "execution_count": 50,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "teacher_actions.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0 25.2\n",
      "50 189.2\n",
      "100 199.3\n",
      "150 200.0\n",
      "200 193.9\n",
      "250 200.0\n",
      "300 200.0\n",
      "350 200.0\n",
      "400 200.0\n",
      "450 200.0\n"
     ]
    }
   ],
   "source": [
    "#模仿学习\n",
    "def copy_learn():\n",
    "    optimizer = torch.optim.Adam(discriminator.parameters(), lr=1e-3)\n",
    "    bce_loss = torch.nn.BCELoss()\n",
    "\n",
    "    for i in range(500):\n",
    "        #使用学生模型获取一局游戏的数据,不需要reward\n",
    "        states, _, actions, next_states, overs = student.get_data()\n",
    "\n",
    "        #使用鉴别器鉴定两批数据是来自教师的还是学生的\n",
    "        prob_teacher = discriminator(teacher_states, teacher_actions)\n",
    "        prob_student = discriminator(states, actions)\n",
    "\n",
    "        #老师的用0表示,学生的用1表示,计算二分类loss\n",
    "        loss_teacher = bce_loss(prob_teacher, torch.zeros_like(prob_teacher))\n",
    "        loss_student = bce_loss(prob_student, torch.ones_like(prob_student))\n",
    "        loss = loss_teacher + loss_student\n",
    "\n",
    "        #调整鉴别器的loss\n",
    "        optimizer.zero_grad()\n",
    "        loss.backward()\n",
    "        optimizer.step()\n",
    "\n",
    "        #使用一批数据来自学生的概率作为reward,取log,再符号取反\n",
    "        #因为鉴别器会把学生数据的概率贴近1,所以目标是让鉴别器无法分辨,这是一种对抗网络的思路\n",
    "        rewards = -prob_student.log().detach()\n",
    "\n",
    "        #更新学生模型参数,使用PPO模型本身的更新方式\n",
    "        student.train(states, rewards, actions, next_states, overs)\n",
    "\n",
    "        if i % 50 == 0:\n",
    "            test_result = sum([student.test(play=False)\n",
    "                               for _ in range(10)]) / 10\n",
    "            print(i, test_result)\n",
    "\n",
    "\n",
    "copy_learn()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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AHClLv7evvh7/HLiMlBOnz4tScrJyYnxTAAAQLROnz4tr9d72TEucyUZY5lwOKzGxkbl5eXp+PHj8ng8sZ5S3AqFQsrJyWEdewBr2XNYy57BOvYc1rJnGGN0+vRpZWdnKyHh8keZxOUelISEBH3jG9+QJHk8Hv6x9ADWseewlj2HtewZrGPPYS2v3tftObmIg2QBAIB1CBQAAGCduA2UlJQUPfroo0pJSYn1VOIa69hzWMuew1r2DNax57CWvS8uD5IFAAB9W9zuQQEAAH0XgQIAAKxDoAAAAOsQKAAAwDpxGSjPP/+8brjhBl133XUqKCjQnj17Yj0l6+zcuVNz585Vdna2XC6XXnvttYjtxhg98sgjysrK0oABA1RYWKiPP/44YsypU6c0f/58eTwepaWladGiRTpz5kwvforYq6qq0pQpUzR48GBlZGRo3rx5amxsjBjT3t6usrIyDRkyRKmpqSopKVFLS0vEmKamJhUXF2vgwIHKyMjQihUr1NnZ2ZsfJabWrl2r/Px85yJXfr9fW7dudbazht335JNPyuVyadmyZc5jrOeVeeyxx+RyuSJuY8eOdbazjjFm4szGjRuN2+02//7v/24OHTpkFi9ebNLS0kxLS0usp2aVN9980/z93/+9efXVV40ks3nz5ojtTz75pPF6vea1114z//Vf/2X+6q/+yowcOdJ8/vnnzphZs2aZiRMnml27dpn//M//NKNGjTL33XdfL3+S2CoqKjIvvviiOXjwoGloaDBz5swxubm55syZM86YBx54wOTk5JiamhrzwQcfmKlTp5o///M/d7Z3dnaa8ePHm8LCQrNv3z7z5ptvmqFDh5rKyspYfKSY+I//+A/z61//2vz3f/+3aWxsND/+8Y9NcnKyOXjwoDGGNeyuPXv2mBtuuMHk5+ebH/zgB87jrOeVefTRR83NN99smpubndvJkyed7axjbMVdoNx6662mrKzMud/V1WWys7NNVVVVDGdlty8GSjgcNj6fzzz11FPOY21tbSYlJcX84he/MMYYc/jwYSPJ7N271xmzdetW43K5zB/+8Idem7ttWltbjSRTW1trjLmwbsnJyWbTpk3OmA8//NBIMnV1dcaYC7GYkJBgAoGAM2bt2rXG4/GYjo6O3v0AFrn++uvNv/7rv7KG3XT69GkzevRoU11dbf7iL/7CCRTW88o9+uijZuLEiZfcxjrGXlx9xXPu3DnV19ersLDQeSwhIUGFhYWqq6uL4cziy7FjxxQIBCLW0ev1qqCgwFnHuro6paWlafLkyc6YwsJCJSQkaPfu3b0+Z1sEg0FJUnp6uiSpvr5e58+fj1jLsWPHKjc3N2ItJ0yYoMzMTGdMUVGRQqGQDh061Iuzt0NXV5c2btyos2fPyu/3s4bdVFZWpuLi4oh1k/g3Ga2PP/5Y2dnZuvHGGzV//nw1NTVJYh1tEFd/LPCPf/yjurq6Iv4xSFJmZqY++uijGM0q/gQCAUm65Dpe3BYIBJSRkRGxPSkpSenp6c6Y/iYcDmvZsmW67bbbNH78eEkX1sntdistLS1i7BfX8lJrfXFbf3HgwAH5/X61t7crNTVVmzdvVl5enhoaGljDKG3cuFG//e1vtXfv3i9t49/klSsoKND69es1ZswYNTc3a/Xq1br99tt18OBB1tECcRUoQCyVlZXp4MGDevfdd2M9lbg0ZswYNTQ0KBgM6le/+pVKS0tVW1sb62nFnePHj+sHP/iBqqurdd1118V6OnFt9uzZzs/5+fkqKCjQiBEj9Mtf/lIDBgyI4cwgxdlZPEOHDlViYuKXjqJuaWmRz+eL0aziz8W1utw6+nw+tba2Rmzv7OzUqVOn+uVal5eXa8uWLXrnnXc0fPhw53Gfz6dz586pra0tYvwX1/JSa31xW3/hdrs1atQoTZo0SVVVVZo4caKeffZZ1jBK9fX1am1t1Z/92Z8pKSlJSUlJqq2t1XPPPaekpCRlZmaynt2Ulpamb37zmzpy5Aj/Li0QV4Hidrs1adIk1dTUOI+Fw2HV1NTI7/fHcGbxZeTIkfL5fBHrGAqFtHv3bmcd/X6/2traVF9f74zZvn27wuGwCgoKen3OsWKMUXl5uTZv3qzt27dr5MiREdsnTZqk5OTkiLVsbGxUU1NTxFoeOHAgIviqq6vl8XiUl5fXOx/EQuFwWB0dHaxhlGbMmKEDBw6ooaHBuU2ePFnz5893fmY9u+fMmTM6evSosrKy+Hdpg1gfpRutjRs3mpSUFLN+/Xpz+PBhs2TJEpOWlhZxFDUuHOG/b98+s2/fPiPJ/PM//7PZt2+f+f3vf2+MuXCacVpamnn99dfN/v37zZ133nnJ04y/9a1vmd27d5t3333XjB49ut+dZrx06VLj9XrNjh07Ik5F/Oyzz5wxDzzwgMnNzTXbt283H3zwgfH7/cbv9zvbL56KOHPmTNPQ0GC2bdtmhg0b1q9ORXz44YdNbW2tOXbsmNm/f795+OGHjcvlMr/5zW+MMazh1fqfZ/EYw3peqYceesjs2LHDHDt2zLz33numsLDQDB061LS2thpjWMdYi7tAMcaYn/3sZyY3N9e43W5z6623ml27dsV6StZ55513jKQv3UpLS40xF041/slPfmIyMzNNSkqKmTFjhmlsbIx4jU8//dTcd999JjU11Xg8HvP973/fnD59OgafJnYutYaSzIsvvuiM+fzzz83f/d3fmeuvv94MHDjQ/PVf/7Vpbm6OeJ3f/e53Zvbs2WbAgAFm6NCh5qGHHjLnz5/v5U8TO3/7t39rRowYYdxutxk2bJiZMWOGEyfGsIZX64uBwnpemXvuucdkZWUZt9ttvvGNb5h77rnHHDlyxNnOOsaWyxhjYrPvBgAA4NLi6hgUAADQPxAoAADAOgQKAACwDoECAACsQ6AAAADrECgAAMA6BAoAALAOgQIAAKxDoAAAAOsQKAAAwDoECgAAsA6BAgAArPP/AF6bcxvA7lvLAAAAAElFTkSuQmCC",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/plain": [
       "200.0"
      ]
     },
     "execution_count": 49,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "student.test(play=True)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Gym",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.16"
  },
  "orig_nbformat": 4
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
